//
// Created by martin on 8/30/21.
//

#include "STOUaction.h"
#include "socket.h"
#include "error.h"
#include "cmdaction.h"
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>


#define FILENAME_SUFFIX_NUM_MAX INT32_MAX

static void data_conn_event_cb(struct bufferevent *bev, short what, void *ctx);
static void data_conn_read_cb(struct bufferevent *bev, void *ctx);
static void STOU_close(Session *sess);

static void data_conn_event_cb(struct bufferevent *bev, short what, void *ctx)
{
    Session *sess = (Session *)ctx;
    int finished = 0;
    if (what & BEV_EVENT_CONNECTED) {
        printf("STOU data_conn_event_cb BEV_EVENT_CONNECTED\n");
        send_reply(sess->bev, 125, "Data connection already opened; transfer starting.");
//        bufferevent_trigger(sess->data_bev, EV_READ, 0);
    }
    else if (what & BEV_EVENT_EOF){
        struct evbuffer *input = bufferevent_get_input(sess->data_bev);
        printf("Got a close from client. %ld bytes left in the evbuffer\n", evbuffer_get_length(input));
        send_reply(sess->bev, 226, "Transfer complete.");
        finished = 1;
    }
    else if (what & BEV_EVENT_ERROR){
        printf("Got an error: %s\n", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
        finished = 1;
    }
    else if (what & BEV_EVENT_TIMEOUT){
        printf("STOU data_conn_event_cb BEV_EVENT_TIMEOUT\n");
        send_reply(sess->bev, 426, "Timeout. Connection closed.");
        finished = 1;
    }

    if (finished) {
        STOU_close(sess);
    }
}

static void data_conn_read_cb(struct bufferevent *bev, void *ctx)
{
    Session *sess = (Session *)ctx;
    char data[LINE_MAX];
    size_t nread;
    ssize_t nwrite;

    do {
        nread = bufferevent_read(sess->data_bev, data, sizeof(data));
        if (nread == 0) break; /* 对端发送完毕时，发送EOF(FIN分节)，会触发BEV_EVENT_EOF事件 */

        nwrite = write(sess->recv_data_fd, data, nread);
        if (nwrite < 0) {
            err_ret("data_conn_read_cb write error");
            return;
        }
    } while (nread > 0);
}

static void STOU_close(Session *sess)
{
    printf("STOU_close\n");
    if (!sess) return;
    if (sess->data_bev) {
        bufferevent_free(sess->data_bev);
        sess->data_bev = NULL;
    }

    if (sess->recv_data_fd >= 0) {
        close(sess->recv_data_fd);
        sess->recv_data_fd = -1;
    }

    if (sess->offset > 0) {
        sess->offset = 0;
    }
}

/**
 * 上传文件（名称唯一）
 * @param sess 会话信息
 * @return 解析STOU命令结果
 * @note STOU命令 如果文件不存在，就新建文件；如果文件已经存在，就加上后缀名，确保文件名唯一性
 */
int STOU_action(Session *sess)
{
    if (!sess) return -1;

    // 获取待上传文件物理路径
    char file_path_phy[PATH_MAX];
    char file_path_phy_try[PATH_MAX];
    if (dir_getPhysicalPath(sess->dir, sess->cmd_text, file_path_phy, sizeof(file_path_phy)) < 0) {
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return -1;
    }

    // 打开文件，通过添加后缀方式形式，寻找合适文件名
    int fd = -1, i;
    strcpy(file_path_phy_try, file_path_phy);
    for (i = 0; i < FILENAME_SUFFIX_NUM_MAX; ++i) {
        if ((fd = open(file_path_phy_try, O_WRONLY | O_CREAT | O_EXCL | O_NONBLOCK, 0774)) < 0) {
            if (errno == EEXIST) { /* 文件名已存在 */
                snprintf(file_path_phy_try, sizeof(file_path_phy_try), "%s(%d)", file_path_phy, i);
                continue;
            }
            else {
                send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
                return -1;
            }
        }

        // 成功找到一个合适的文件名
        break;
    }

    if (fd < 0) {
        send_reply(sess->bev, 553, "Requested action not taken; File name not allowed.");
        return -1;
    }

    // 如果有偏移offset > 0，就移动文件读写指针位置, 并且清空保存的偏移
    if (sess->offset > 0) {
        if(lseek(fd, sess->offset, SEEK_SET) < 0) {
            err_ret("lseek error");
            sess->offset = 0;
            return -1;
        }
        sess->offset = 0;
    }

    sess->recv_data_fd = fd;

    // PORT模式连接客户端
    if (connect_PORT(sess, data_conn_read_cb, NULL, data_conn_event_cb) < 0) {
        send_reply(sess->data_bev, 425, "Can't open data connection.");
        return -1;
    }
    return 0;
}